package com.github.yoojia.flow;

import com.github.yoojia.flow.impl.ScriptingEventHandler;
import com.github.yoojia.flow.impl.ScriptingEventInterceptor;
import com.github.yoojia.flow.util.AppPaths;
import com.github.yoojia.flow.util.Classes;
import com.github.yoojia.flow.util.ConfigKit;
import com.parkingwang.lang.Try;
import com.parkingwang.lang.data.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import static com.github.yoojia.flow.util.ConfigKit.checkRequired;

/**
 * @author 2017 Yoojia Chen (yoojiachen@gmail.com)
 */
public class GeneralFlow extends NextFlow {

    private static final Logger LOGGER = LoggerFactory.getLogger(GeneralFlow.class);

    protected GeneralFlow() {
        super();
    }

    private static class InstanceHolder {
        private static final GeneralFlow INSTANCE = new GeneralFlow();
    }

    public static GeneralFlow getInstance() {
        return InstanceHolder.INSTANCE;
    }

    /**
     * 以默认配置文件执行初始化操作。
     * 默认配置文件在程序目录下的 flow-config.yml 文件。
     */
    public void initWithDefaultConfig() {
        // 启动时，加载配置文件：
        final File file = AppPaths.pathOf("flow.yml").toFile();
        if (!file.exists()) {
            ConfigKit.extractByName("/flow-sample.yml", "flow.yml");
        }
        initFromFile(file);
    }

    /**
     * 以指定配置文件执行初始化操作。
     */
    public void initFromFile(File configFile) {
        LOGGER.info("Init from config: {}", configFile.getAbsolutePath());
        initFromConfig(Try.die(() -> {
            try (InputStream inputStream = new FileInputStream(configFile)) {
                return ConfigKit.stream(inputStream);
            }
        }));
    }

    /**
     * 以指定配置对象执行初始化操作。
     */
    public void initFromConfig(Config config) {
        mGlobalConfig.copy(config);

        //// 加载核心组件 /////

        // Dispatcher
        initialOrDie(mDispatcher, this, ConfigKit.empty());
        // plugins
        mPlugins.forEach(plugin ->
                initialOrDie(plugin, this, ConfigKit.empty()));
        registerInitComponents(FIELD_NAME_PLUGINS);
        // interceptors
        mInterceptors.forEach(interceptor ->
                initialOrDie(interceptor, this, ConfigKit.empty()));
        registerInitComponents(FIELD_NAME_INTERCEPTORS);
        // transformers
        registerInitComponents(FIELD_NAME_TRANSFORMERS);
        // handlers
        registerInitComponents(FIELD_NAME_HANDLERS);
        // deliveries
        mDeliveries.forEach(delivery ->
                initialOrDie(delivery, this, ConfigKit.empty()));
        registerInitComponents(FIELD_NAME_DELIVERIES);
        // devices
        mDevices.forEach(device ->
                initialOrDie(device, this, ConfigKit.empty()));
        registerInitComponents(FIELD_NAME_DEVICES);

        LOGGER.info("Init loaded Plugins: {}", mPlugins.size());
        LOGGER.info("Init loaded EventInterceptors: {}", mInterceptors.size());
        LOGGER.info("Init loaded EventPipelines: {}", mPipelines.size());
        LOGGER.info("Init loaded EventHandlers: {}", mHandlers.size());
        LOGGER.info("Init loaded EventDeliveries: {}", mDeliveries.size());
        LOGGER.info("Init loaded VirtualDevices: {}", mDevices.size());
    }

    ////

    private void registerInitComponents(String configName) {
        configListOf(configName).forEach(config -> {
            final Object impl = Classes.newInstance(config.getString(FIELD_NAME_IMPLEMENT));
            final Class<?> type = impl.getClass();
            // EventInterceptor
            if (impl instanceof EventInterceptor) {
                registerLogging("EventInterceptor", type);
                install((EventInterceptor) impl);
            } else if (impl instanceof VirtualDevice) {
                checkRequired(config, "address", "Config<address> is REQUIRED for all devices");
                final VirtualDevice device = (VirtualDevice) impl;
                device.setAddress(config.getString("address"));
                registerLogging("VirtualDevice", type);
                install(device);
            } else if (impl instanceof Plugin) {
                registerLogging("Plugin", type);
                install((Plugin) impl);
            } else if (impl instanceof EventPipeline) {
                registerLogging("EventPipeline", type);
                install((EventPipeline) impl);
            } else if (impl instanceof EventHandler) {
                registerLogging("EventHandler", type);
                install((EventHandler) impl);
            } else if (impl instanceof EventDelivery) {
                registerLogging("EventDelivery", type);
                install((EventDelivery) impl);
            } else {
                throw new IllegalArgumentException("Object is NOT flow components, was: " + impl.getClass());
            }
            // Init
            if (impl instanceof Initial) {
                initialOrDie((Initial) impl, this, popConfigField(config, "configs"));
            }
        });
    }

    private Stream<Config> configListOf(String name) {
        return popConfigList(name).stream()
                // 过滤设置了 disabled 为 true 的配置条目
                .filter(config ->
                        !config.getBoolean(FIELD_NAME_DISABLED, false))
                .peek(config -> {
                    if (config.containsName(FIELD_NAME_IMPLEMENT)) {
                        config.put(FIELD_NAME_IMPLEMENT, Classes.resolveImplClass(config.getString(FIELD_NAME_IMPLEMENT)));
                    } else if (config.containsName("script")) {
                        String clazz;
                        if (FIELD_NAME_HANDLERS.equals(name)) {
                            clazz = ScriptingEventHandler.class.getName();
                        } else if (FIELD_NAME_INTERCEPTORS.equals(name)) {
                            clazz = ScriptingEventInterceptor.class.getName();
                        } else {
                            throw new IllegalArgumentException("Illegal script config on " + name);
                        }
                        config.put(FIELD_NAME_IMPLEMENT, clazz);
                        final Map<String, Object> configs = new LinkedHashMap<>(1);
                        configs.put("script", config.getString("script"));
                        config.put("configs", configs);
                        config.getDataMap().remove("script");
                    }
                });
    }

    private List<Config> popConfigList(String configName) {
        try {
            return mGlobalConfig.getConfigList(configName);
        } finally {
            mGlobalConfig.getDataMap().remove(configName);
        }
    }

    private Config popConfigField(Config source, String configName) {
        try {
            return source.getConfig(configName);
        } finally {
            // 节点配置信息，在初始化完成后，清除内存中的配置数据
            source.remove(configName);
        }
    }

    private static void initialOrDie(Initial initial, Context context, Config config) {
        try {
            initial.onInit(context, config);
        } catch (Exception e) {
            LOGGER.error("Error when initializing object: " + initial, e);
            System.exit(-1);
        }
    }

    private static void registerLogging(String typeName, Class<?> object) {
        LOGGER.info("Register from config: {}, instance: {}", typeName, object);
    }
}
